package org.carabiner.infinitest;

import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.File;
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;

import org.carabiner.infinitest.depgraph.DependencyGraph;
import org.carabiner.infinitest.depgraph.JavaClass;
import org.carabiner.infinitest.depgraph.RegexVerifier;
import org.carabiner.infinitest.depgraph.TestVerifier;

/**
 * Core class for Infinitest.
 * 
 * @author <a href="mailto:benrady@gmail.com"Ben Rady</a>
 */
public class InfinitestCore
{
  // FIXME Replace with enumeration...or maybe state pattern?
  private static final String PAUSED_STATUS = "Tests Paused";
  private static final String SCANNING_STATUS = "Scanning For Tests";
  private static final String RUNNING_STATUS = "Running Test";
  private static final String WAITING_STATUS = "Waiting For Changes";
  
  public static final String TESTS_IN_QUEUE_PROPERTY = "Queued Tests";
  public static final String CURRENT_TEST_PROPERTY = "Current Test";
  public static final String RUN_TESTS_PROPERTY = "Run Tests";
  public static final String STATUS_PROPERTY = "status";
  
  private File filterFile;
  private TestRunner runner;
  private DependencyGraph index;
  private FilterList filterList;
  private boolean runTests = true;
  private PropertyChangeSupport pcs;
  private FileChangeDetector changeDetector;
  private String status;

  public InfinitestCore(File filterListFile, boolean multiThreaded, File path) throws IOException
  {     
    if (filterListFile == null)
      filterFile = new File("infinitest.filters");
    else
      filterFile = filterListFile;       
    
    status = SCANNING_STATUS;
    pcs = new PropertyChangeSupport(this);
    if (multiThreaded)
    {
      runner = new ExternalProcessTestRunner(JUnitRunner.class);  
    }
    else
    {
      runner = new JUnitRunner();      
    }
    
    filterList = new FilterList(filterFile);
    TestVerifier verifier = new RegexVerifier(filterList);
    changeDetector = new FileChangeDetector(path);
    index = new DependencyGraph(path, verifier);
    Timer timer = new Timer();
    if (multiThreaded)
    {
      timer.schedule(new TimerTask()
      {       
        public void run()
        {
          update();          
        }        
      }, 100, InfinitestSettings.getRefreshRate());    
    }
  }
  
  public boolean shouldRunTests()
  {
    return runTests;
  }

  public void setRunTests(boolean shouldRunTests)
  {
    boolean oldValue = runTests;
    runTests = shouldRunTests;
    if (runTests)
    {
      reload();            
    }
    else
    {      
      setStatus(PAUSED_STATUS);
      runner.killTest();
    }    
    pcs.firePropertyChange(RUN_TESTS_PROPERTY, oldValue, runTests);
  }
  
  public void update()
  {
    try
    {      
      setStatus(WAITING_STATUS);      
      Set<JavaClass> testsToRun = index.findTestsToRun(changeDetector.findChangedFiles());
      if (runTests)
      {
        if (!testsToRun.isEmpty())
        {
          pcs.firePropertyChange(TESTS_IN_QUEUE_PROPERTY, 0, testsToRun.size());               
          Set<String> testNames = getNames(testsToRun);        
          int testsLeft = testsToRun.size();
          for (String testName : testNames)
          {
            setStatus(RUNNING_STATUS + " " + testName);
            pcs.firePropertyChange(CURRENT_TEST_PROPERTY, null, testName);          
            runner.runTest(testName);          
            pcs.firePropertyChange(TESTS_IN_QUEUE_PROPERTY, testsLeft, --testsLeft);
            if (!runTests)
            {
              setStatus(PAUSED_STATUS);
              break;
            }
          }      
          setStatus(WAITING_STATUS);
        }        
      }
      else
      {
        setStatus(PAUSED_STATUS);
      }
    }
    catch (Exception ex)
    {
      ex.printStackTrace();
    }   
  }
  
  private void setStatus(String newStatus)
  {
    String oldStatus = status;
    status = newStatus;    
    pcs.firePropertyChange(STATUS_PROPERTY, oldStatus, status);
  }

  Set<String> getNames(Set<JavaClass> testsToRun)
  {
    Set<String> names = new HashSet<String>();
    for(JavaClass clazz : testsToRun)
    {
      names.add(clazz.getName());
    }
    return names;
  }

  public FilterList getFilters()
  {
    return filterList;
  }
  
  public String getStatus()
  {
    return status;
  }

  public void addPropertyChangeListener(String propertyName, PropertyChangeListener l)
  {
    if (propertyName != null)
      pcs.addPropertyChangeListener(propertyName, l);
    else
      pcs.addPropertyChangeListener(l);
  }

  public void addTestStatusListener(TestStatusListener l)
  {
    runner.addTestStatusListener(l);
  }

  public void reload()
  {
    setStatus(SCANNING_STATUS);
    index.clear();   
    changeDetector.clear();
  }

}
